package test;

import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.w3c.dom.Node;

import topology.Custom;
import topology.Full;
import topology.OneToOne;
import topology.Topology;

import activation.ActivationFunction;
import activation.Linear;
import activation.SigmoidLog;
import ann.*;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import learning.ErrorMinimizing;
import learning.LearningRule;
import learning.NoLearning;

public class NeuralNetworkXmlParser {

	private static class Environment{
		
		public Environment()
		{
			learningRule = null;
			minInitialWeight = 0;
			maxInitialWeight =  0;
			activationFunction = null;
			learningRate = 0;
			momentum = 0;
			topology = null;
		}
		
		public LearningRule learningRule;
		public ActivationFunction activationFunction;
		public Topology topology;
		public double minInitialWeight;
		public double maxInitialWeight;
		public double learningRate;
		public double momentum;
		
		public Environment clone()
		{
			Environment env = new Environment();
			env.learningRule = learningRule;
			env.minInitialWeight = minInitialWeight;
			env.maxInitialWeight = maxInitialWeight;
			env.activationFunction = activationFunction;
			env.learningRate = learningRate;
			env.momentum = momentum;
			env.topology = topology.clone();
			return env;
		}
	}
	
	private static class ModuleLink{
		public ModuleLink(Module module,String id){
			this.module = module;
			this.id = id;
		}
		public Module module;
		public String id;
	}

	public static Module readNeuralNetwork(String str) {

		try {
			
			File fXmlFile = new File(str);
			DocumentBuilder dBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
			Document doc = dBuilder.parse(fXmlFile);
			doc.getDocumentElement().normalize();
			
			for(int i  = 0; i < doc.getChildNodes().getLength(); i++){
				Element item = (Element)doc.getChildNodes().item(i);
				if(item.getTagName()=="NeuralNetwork")
				{
					return parseSubNetwork(item,new Environment()).module;
				}
			}
		} 
		catch (Exception e) { e.printStackTrace(); }
		return null;
	}

	private static ModuleLink parseSubNetwork(Element subNetwork, Environment environment){
		HashMap<String, Module> modules = new HashMap<String, Module>();
		SubNetwork subNet = new SubNetwork();
		String id = "Not Identified";
		
		List<Element> links = new ArrayList<Element>();
		List<Element> subNetworks = new ArrayList<Element>();
		List<Element> layers = new ArrayList<Element>();
		
		for(int i  = 0; i < subNetwork.getChildNodes().getLength(); i++){
			Node node = subNetwork.getChildNodes().item(i);
			if(node.getNodeType()!=Node.ELEMENT_NODE)
				continue;
			Element item = (Element)node;
			if(item.getTagName()=="SubNetwork")
			{
				subNetworks.add(item);
			}
			else if(item.getTagName()=="Layer")
			{
				layers.add(item);
			}
			else if(item.getTagName()=="Link")
			{
				links.add(item);
			}
			else if(item.getTagName()=="TextId")
			{
				id = item.getTextContent();
			}
			else if(item.getTagName()=="LearningRule")
			{
				environment.learningRule = parseLearningRule(item);
			}
			else if(item.getTagName()=="MinInitialWeight")
			{
				environment.minInitialWeight = Double.parseDouble(item.getTextContent());
			}
			else if(item.getTagName()=="MaxInitialWeight")
			{
				environment.maxInitialWeight = Double.parseDouble(item.getTextContent());
			}
			else if(item.getTagName()=="LearningRate")
			{
				environment.learningRate = Double.parseDouble(item.getTextContent());
			}
			else if(item.getTagName()=="Momentum")
			{
				environment.momentum = Double.parseDouble(item.getTextContent());
			}
			else if(item.getTagName()=="ActivationFunction")
			{
				environment.activationFunction = parseActivationFunction(item);
			}
			else if(item.getTagName()=="Topology")
			{
				environment.topology = parseTopology(item);
			}
			else{
				System.out.println("Error: "+item.getTagName()+" not recognized!");
			}
		}
		
		for(Element item : subNetworks)
		{
			ModuleLink ml = parseSubNetwork(item,environment.clone());
			modules.put(ml.id, ml.module);
			subNet.addSubModule(ml.module);
		}
		for(Element item : layers)
		{
			ModuleLink ml = parseLayer(item,environment.clone());
			modules.put(ml.id, ml.module);
			subNet.addSubModule(ml.module);
		}
		for(Element item : links)
		{
			parseAndCreateLink(item,modules,environment.clone());
		}
		
		ModuleLink ret = new ModuleLink(subNet,id);
		return ret;
	}
	
	private static Topology parseTopology(Element topologyElement) {
		for(int i  = 0; i < topologyElement.getChildNodes().getLength(); i++){
			Node node = topologyElement.getChildNodes().item(i);
			if(node.getNodeType()!=Node.ELEMENT_NODE)
				continue;
			Element item = (Element)node;
			
			if(item.getTagName()=="Full")
			{
				return new Full();
			}
			else if(item.getTagName()=="OneToOne")
			{
				return new OneToOne();
			}
			else if(item.getTagName()=="Custom")
			{
				return new Custom();
			}
			else{
				System.out.println("Error: "+item.getTagName()+" not recognized!");
			}
		}
		return null;
	}

	private static ModuleLink parseLayer(Element layer, Environment environment) {
		int size = 0;
		String id = "Not identified";
		
		boolean hasBias = false;
		
		for(int i  = 0; i < layer.getChildNodes().getLength(); i++){
			Node node = layer.getChildNodes().item(i);
			if(node.getNodeType()!=Node.ELEMENT_NODE)
				continue;
			Element item = (Element)node;
			
			if(item.getTagName()=="TextId")
			{
				id = item.getTextContent();
			}
			else if(item.getTagName()=="LayerSize")
			{
				size = Integer.parseInt(item.getTextContent());
			}
			else if(item.getTagName()=="ActivationFunction")
			{
				environment.activationFunction = parseActivationFunction(item);
			}
			else if(item.getTagName()=="BiasLayer")
			{
				hasBias = true;
				for(int j  = 0; j < item.getChildNodes().getLength(); j++){
					Node biasNode = item.getChildNodes().item(j);
					if(biasNode.getNodeType()!=Node.ELEMENT_NODE)
						continue;
					Element biasItem = (Element)biasNode;
					
					if(biasItem.getTagName()=="MinInitialWeight"){
						environment.minInitialWeight = Double.parseDouble(biasItem.getTextContent());
					}
					else if(biasItem.getTagName()=="MaxInitialWeight"){
						environment.maxInitialWeight = Double.parseDouble(biasItem.getTextContent());
					}
					else if(biasItem.getTagName()=="LearningRule"){
						environment.learningRule = parseLearningRule(biasItem);
					}
					else if(biasItem.getTagName()=="LearningRate"){
						environment.learningRate = Double.parseDouble(biasItem.getTextContent());
					}
					else if(biasItem.getTagName()=="Momentum"){
						environment.momentum = Double.parseDouble(biasItem.getTextContent());
					}
					else
					{
						System.out.println("Error: "+biasItem.getTagName()+" not recognized!");
					}
				}
			}
			else{
				System.out.println("Error: "+item.getTagName()+" not recognized!");
			}
		}
		Layer returnLayer = new Layer(size, environment.activationFunction,id);
		if(hasBias)
			returnLayer.addBiasLayer(environment.learningRule, environment.learningRate, environment.minInitialWeight, environment.maxInitialWeight, environment.momentum);
		return new ModuleLink(returnLayer,id);
	}

	private static ActivationFunction parseActivationFunction(Element act) {
		
		for(int i  = 0; i < act.getChildNodes().getLength(); i++){
			Node node = act.getChildNodes().item(i);
			if(node.getNodeType()!=Node.ELEMENT_NODE)
				continue;
			Element item = (Element)node;
			
			if(item.getTagName()=="Linear")
			{
				return new Linear();
			}
			else if (item.getTagName()=="SigmoidLog"){
				return parseSigmoidLog(item);
			}
			else{
				System.out.println("Error: "+item.getTagName()+" not recognized!");
			}
		}
		return null;
	}

	private static SigmoidLog parseSigmoidLog(Element sigmoid) {
		double k=1;
		double min = 0;
		double max = 1;
		
		for(int i  = 0; i < sigmoid.getChildNodes().getLength(); i++){
			Node node = sigmoid.getChildNodes().item(i);
			if(node.getNodeType()!=Node.ELEMENT_NODE)
				continue;
			Element item = (Element)node;
			
			if(item.getTagName()=="K")
			{
				k = Double.parseDouble(item.getTextContent());
			}
			else if(item.getTagName()=="Min")
			{
				min = Double.parseDouble(item.getTextContent());
			}
			else if(item.getTagName()=="Max")
			{
				max = Double.parseDouble(item.getTextContent());
			}
			else{
				System.out.println("Error: "+item.getTagName()+" not recognized!");
			}
		}
		return new SigmoidLog(k,min,max);
	}

	private static void parseAndCreateLink(Element linkElement, HashMap<String, Module> modules, Environment  environment) {
		
		Module fromModule = null;
		Module toModule = null;
		
		HashMap<Edge,Double> customEdges = new HashMap<Edge,Double>();
		
		for(int i  = 0; i < linkElement.getChildNodes().getLength(); i++){
			Node node = linkElement.getChildNodes().item(i);
			if(node.getNodeType()!=Node.ELEMENT_NODE)
				continue;
			Element item = (Element)node;
			
			if(item.getTagName()=="FromModuleId"){
				String id = item.getTextContent();
				if(modules.containsKey(id))
				{
					fromModule = modules.get(id);
				}
				else{
					System.out.println("Error: "+item.getTextContent()+" not found!");
				}
			}
			else if(item.getTagName()=="ToModuleId"){
				String id = item.getTextContent();
				if(modules.containsKey(id))
				{
					toModule = modules.get(id);
				}
				else{
					System.out.println("Error: "+item.getTextContent()+" not found!");
				}
			}
			else if(item.getTagName()=="Topology"){
				environment.topology = parseTopology(item);
			}
			else if(item.getTagName()=="MinInitialWeight"){
				environment.minInitialWeight = Double.parseDouble(item.getTextContent());
			}
			else if(item.getTagName()=="MaxInitialWeight"){
				environment.maxInitialWeight = Double.parseDouble(item.getTextContent());
			}
			else if(item.getTagName()=="LearningRule"){
				environment.learningRule = parseLearningRule(item);
			}
			else if(item.getTagName()=="LearningRate"){
				environment.learningRate = Double.parseDouble(item.getTextContent());
			}
			else if(item.getTagName()=="Momentum"){
				environment.momentum = Double.parseDouble(item.getTextContent());
			}
			else if(item.getTagName()=="Weights")
			{
				for(int j  = 0; j < item.getChildNodes().getLength(); j++){
					Node weightNode = item.getChildNodes().item(j);
					if(weightNode.getNodeType()!=Node.ELEMENT_NODE)
						continue;
					Element weightItem = (Element)weightNode;
					
					if(weightItem.getTagName()=="Weight"){
						int from  = Integer.parseInt(weightItem.getAttribute("from"));
						int to  = Integer.parseInt(weightItem.getAttribute("to"));
						double weight;
						if(weightItem.hasAttribute("weight"))
							weight  = Double.parseDouble(weightItem.getAttribute("weight"));
						else{
							weight = 10000;
						}
						customEdges.put(new Edge(from,to), weight);
					}
					else
					{
						System.out.println("Error: "+weightItem.getTagName()+" not recognized!");
					}
				}
			}
			else{
				System.out.println("Error: "+item.getTagName()+" not recognized!");
			}
		}
		if(Custom.class.isInstance(environment.topology))
		{
			for(Edge edge : customEdges.keySet())
				((Custom)environment.topology).addEdge(edge.from, edge.to);
			((Custom)environment.topology).setReady();
		}
		Link link = Link.setupLink(fromModule, toModule, environment.topology, environment.learningRule, environment.learningRate, environment.minInitialWeight, environment.maxInitialWeight, environment.momentum);
		for(Edge edge : customEdges.keySet())
		{
			if(customEdges.get(edge)!=10000)
			{
				//System.out.println("Setting ("+edge.from+", "+edge.to+") to "+customEdges.get(edge));
				link.setWeight(edge.from, edge.to, customEdges.get(edge));
			}
		}
	}

	private static LearningRule parseLearningRule(Element learning) {
		for(int i  = 0; i < learning.getChildNodes().getLength(); i++){
			Node node = learning.getChildNodes().item(i);
			if(node.getNodeType()!=Node.ELEMENT_NODE)
				continue;
			Element item = (Element)node;
			
			if(item.getTagName()=="ErrorMinimizing")
			{
				return new ErrorMinimizing();
			}
			else if(item.getTagName()=="NoLearning")
			{
				return new NoLearning();
			}
			else{
				System.out.println("Error: "+item.getTagName()+" not recognized!");
			}
		}
		return null;
	}

	public static void main(String[] args) {
		Module mod = readNeuralNetwork("neuralNetworks//hardwiredNetwork.xml");
		mod.updateOutput();
		System.out.println(mod);
	}
}